﻿using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using System.Reflection.Metadata;

namespace WaferService
{
    public class FoupService(ILogger<SubstrateTrackingService> logger,
    IOptions<EFEMConfig> options,
    IEFEMService efemService,
    IDbContextFactory<ApplicationDbContext> factory,
    SubstrateTrackingService substrateTrackingService)
    {
        readonly EFEMConfig config = options.Value;

        public Guid Open(int foup)
        {
            Check(foup);

            var id = Guid.NewGuid();
            _ = OpenAsync(id, foup);
            return id;
        }

        public Guid Close(int foup)
        {
            Check(foup);

            var id = Guid.NewGuid();
            _ = CloseAsync(id, foup);
            return id;
        }

        async Task OpenAsync(Guid guid, int foup)
        {
            try
            {
                await efemService.Open(Parameter.LoadPort(foup));
                var mapping = await efemService.GetWaferMapping(Parameter.LoadPort(foup));
                var states = new WaferState[mapping.Length];
                for (var i = 0; i < mapping.Length; i++)
                {
                    states[i] = mapping[i] switch
                    {
                        '0' => WaferState.Absent,
                        '1' => WaferState.Present,
                        _ => WaferState.MappingError,
                    };
                }

                using var ctx = factory.CreateDbContext();
                using var transaction = ctx.Database.BeginTransaction();
                var stray = await ctx.Substrates
                    .Where(s => s.Foup == foup && s.Location != WaferLocation.Foup)
                    .Select(s => new
                    {
                        s.Slot,
                        s.State
                    })
                    .ToListAsync();
                foreach (var s in stray)
                {
                    states[s.Slot - 1] = s.State;
                }
                await ctx.SaveChangesAsync();
                await transaction.CommitAsync();

                await substrateTrackingService.SendEvent(new FoupOpenEvent
                {
                    TrackingId = guid,
                    Foup = foup,
                    Slots = states
                });
            }
            catch (Exception ex)
            {
                logger.LogError(ex, "Open LoadPort{foup} failed", foup);

                await substrateTrackingService.SendEvent(new FoupErrorEvent
                {
                    TrackingId = guid,
                    Foup = foup,
                    Message = ex.Message
                });
            }
        }

        async Task CloseAsync(Guid guid, int foup)
        {
            try
            {
                await efemService.Close(Parameter.LoadPort(foup));

                using var ctx = factory.CreateDbContext();
                await substrateTrackingService.SendEvent(new FoupClosedEvent
                {
                    TrackingId = guid,
                    Foup = foup
                });
            }
            catch (Exception ex)
            {
                logger.LogError(ex, "Close LoadPort{foup} failed", foup);

                await substrateTrackingService.SendEvent(new FoupErrorEvent
                {
                    TrackingId = guid,
                    Foup = foup,
                    Message = ex.Message
                });
            }
        }

        void Check(int foup)
        {
            if (foup < 1 || foup > config.LoadPort)
            {
                throw new BadHttpRequestException($"Foup{foup} does not exist.");
            }
        }
    }
}
